package bayes;

import engine.Stats;
import engine.Tweet.Brand;
import engine.Tweet.Polarity;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;
import javax.swing.SwingWorker;
import normalisation.Normalizer;
import ui.UserInterface;
import utilities.FileTools;

/*
 * aPriori[k] = # tweets dans la classe k / nb tweets
 * vraisemblanceMot[i][k] = # mot i dans classe k / # mots total classe k
 * vraisemblanceTweet pour une classe k = pour tout les mots i du tweet, produit(vraisemblanceMot[i][k])
 * evidence = pour tte classe k , Somme sur k de (aPriori[k]*vraisemblanceTweet pour k)
 * aPosteriori pour une classe k = vraisemblanceTweet * aPriori[k] / evidence
 * La classe avec le plus grand a Posteriori est la "bonne"
 */
public class MultiSwing extends SwingWorker<Void, Integer> {

    private UserInterface gui;
    private String filePath;
    private Normalizer normalizer;
    private TreeMap<String, Integer>[] tfMapsPolarity;//tf maps de chaque classe pour le calcul de vraisemblance
    private TreeMap<String, Integer>[] tfMapsBrand;
    private Double[] aPriorisPolarity;//l'a priori de chaque classe
    private Double[] aPriorisBrand;
    private Integer classesCount;
    private Double sucessRatePolarity, sucessRateBrand;
    private String outputPath;
    private Integer[][] matriceConfusion;
    private Integer penalityPoints;
    private Double[] coeffsAntiPenality;
    private Integer vocabularySize;

    public MultiSwing(UserInterface gui,
            String filePath,
            Normalizer normalizer,
            TreeMap<String, Integer>[] tfMapsPolarity,
            TreeMap<String, Integer>[] tfMapsBrand,
            Double[] aPriorisPolarity,
            Double[] aPriorisBrand,
            Double[] coeffsAntiPenality,
            String outputPath,
            Integer vocabularySize) {

        this.gui = gui;
        this.filePath = filePath;
        this.outputPath = outputPath;
        this.normalizer = normalizer;
        this.tfMapsPolarity = tfMapsPolarity;
        this.tfMapsBrand = tfMapsBrand;
        this.aPriorisPolarity = aPriorisPolarity;
        this.aPriorisBrand = aPriorisBrand;
        this.matriceConfusion = new Integer[Polarity.values().length][Polarity.values().length];
        this.penalityPoints = 0;
        this.coeffsAntiPenality = coeffsAntiPenality;
        this.vocabularySize = vocabularySize;

        this.classesCount = aPriorisPolarity.length;
    }

    @Override
    protected Void doInBackground() throws Exception {
        //Parcours train, print best class pol et brand pour chaque tweet

        //Parcours de chaque ligne
        List<String> lines = FileTools.readLinesTextFile(filePath, normalizer.getCharset());
        Polarity currentPolarity;
        Brand currentBrand;
        Integer lineCount = 0;
        Integer rigthGuessPolarity = 0;
        Integer rigthGuessBrand = 0;
        Integer flagedTweet = 0;

        //init matrice confusion
        for (int i = 0; i < matriceConfusion.length; i++) {
            for (int j = 0; j < matriceConfusion.length; j++) {
                matriceConfusion[i][j] = 0;
            }
        }

        Writer writer = (outputPath != null) ? new OutputStreamWriter(new FileOutputStream(outputPath)) : null;

        for (String line : lines) {
            //System.out.println("\nline: " + (lineCount + 1));

            //suppression première parenthèse (doit être le premier caractère)
            line = line.substring(1);

            //parse la polarité
            currentPolarity = Stats.parsePolarity(line.substring(0, line.indexOf(",")));
            line = line.substring(line.indexOf(",") + 1);//on supprime "polarité,"

            if (currentPolarity != Polarity.unknown) {
                flagedTweet++;
            }

            //parse la marque
            currentBrand = Stats.parseBrand(line.substring(0, line.indexOf(")")));
            line = line.substring(line.indexOf(")") + 2);//on supprime "marque) "

            //Mots du tweet
            ArrayList<String> words = normalizer.normalize(line);

            Integer bestPolId = bestPosterioriClass(words, tfMapsPolarity, aPriorisPolarity);
            Polarity bestPol = Polarity.values()[bestPolId];
            //System.out.println("Polarity:" + bestPol + " ("+currentPolarity+")");

            if (currentPolarity == bestPol) {
                rigthGuessPolarity++;
            }

            matriceConfusion[bestPolId][currentPolarity.ordinal()]++;

            if (writer != null) {
                writer.write(bestPol.toString() + "\n");
            }

            Integer bestBrandId = bestPosterioriClass(words, tfMapsBrand, aPriorisBrand);
            Brand bestBrand = Brand.values()[bestBrandId];
            //System.out.println("Brand:" + bestBrand + " ("+currentBrand+")");

            if (currentBrand == bestBrand) {
                rigthGuessBrand++;
            }

            lineCount++;
        }

        if (writer != null) {
            writer.close();
        }

        if (flagedTweet == 0) {
            sucessRatePolarity = (double) rigthGuessPolarity * 100 / lineCount;
        } else {
            sucessRatePolarity = (double) rigthGuessPolarity * 100 / flagedTweet;
        }

        //sucessRatePolarity = (double) rigthGuessPolarity * 100 / lineCount;
        sucessRateBrand = (double) rigthGuessBrand * 100 / lineCount;

        //calcul des points de penalité
        Integer[][] matricePenalite = {
            {0, 2, 3, 3},
            {2, 0, 3, 3},
            {4, 4, 0, 1},
            {4, 4, 1, 0},};

        for (int i = 0; i < matriceConfusion.length - 1; i++) {
            for (int j = 0; j < matriceConfusion.length - 1; j++) {
                this.penalityPoints += matriceConfusion[i][j] * matricePenalite[i][j];
            }
        }

        //afficher matrice
        if (flagedTweet != 0) {
            //System.out.println("Matrice de Confusion: ");

            System.out.print("     ");
            for (int i = 0; i < Polarity.values().length - 1; i++) {
                System.out.print(Polarity.values()[i].toString().substring(0, 5) + " ");
            }
            System.out.println("     ");
            for (int i = 0; i < matriceConfusion.length - 1; i++) {
                System.out.print(Polarity.values()[i].toString().substring(0, 5));
                for (int j = 0; j < matriceConfusion.length - 1; j++) {
                    System.out.format(" %4d ", matriceConfusion[i][j]);
                }
                System.out.println("");
            }
        }
        return null;
    }

    //Id de la classe avec le meilleur posteriori
    private Integer bestPosterioriClass(ArrayList<String> words, TreeMap<String, Integer>[] tfMapClasses, Double[] aPrioris) {
        //Calcul de l'evidence
        //Double evidence = evidence(words, tfMapClasses, aPrioris);

        //Calcul du posteriori pour chaque classe
        Double currentPosteriori;
        Double bestPosteriori = -Double.MAX_VALUE;
        Integer matchClass = -1;
        Double tmp;
        for (int k = 0; k < classesCount; k++) {
            tmp = vraisemblanceTweet(k, words, tfMapClasses);
            //System.out.println("k" + k + "; V:" + tmp);
            currentPosteriori = (tmp * aPrioris[k]);
            if (currentPosteriori > bestPosteriori) {
                bestPosteriori = currentPosteriori;
                matchClass = k;
            }
        }

        return matchClass;
    }

    /*
     * Calcul la vraisemblance des mots du tweet à l'id de la classe en parametre
     * Vraisemblance d'un mot pour une classe k = nb fois le mot dans la classe k/ nb mots total dans la classe k
     * Hypothèse naive : vraisemblance du tweet = produit des vraisemblance de chaque mot
     * Vraisemblance nulle si la classe ne contient aucun mot
     * Pour régler le pb de Soupassement arithmétique, on additionne les logarithmes
     */
    private Double vraisemblanceTweet(Integer k, ArrayList<String> words, TreeMap<String, Integer>[] tfMapClasses) {

        Double result = Double.MAX_VALUE;
        Double currentTf;

        if (tfMapClasses[k].size() == 0) {
            return 0.;
        }

        //Pour chaque mot du tweet
        for (String word : words) {
            currentTf = (tfMapClasses[k].containsKey(word)) ? tfMapClasses[k].get(word) : 0.;
            //System.out.println(word+": " + (currentTf+1) + "; size:" + tfMapClasses[k].size());
            result *= (double) ((coeffsAntiPenality[k] * (currentTf)) + 0.01) / vocabularySize;//tfMapClasses[k].size();
        }
        return result;
    }

    /*
     * evidence = pour tte classe k , Somme sur k de (aPriori[k]*vraisemblanceTweet pour k)
     */
    private Double evidence(ArrayList<String> words, TreeMap<String, Integer>[] tfMapClasses, Double[] aPrioris) {
        Double result = 0d;

        for (int k = 0; k < classesCount; k++) {
            result += aPrioris[k] * vraisemblanceTweet(k, words, tfMapClasses);
        }

        if (result == 0d) {
            result = 1d;
        }

        return result;
    }

    @Override
    protected void done() {
        gui.updateBayesResult(sucessRatePolarity, sucessRateBrand, penalityPoints);
    }
}